/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package algorithm;

import java.util.ArrayList;
import static java.util.Collections.shuffle;
import java.util.Random;

/**
 *
 * @author Nuf
 */
public class GeneticAlgo {

    private Population population;

    public void setPopulation(Population population) {
        this.population = population;
        this.bestCurrentSolution = population.getBestSolution();
    }
    private Solution bestCurrentSolution;
    private ProblemData data;

    public GeneticAlgo(int numberOfSolutions, ProblemData data) {
        this.data = data;
        this.population = new Population(numberOfSolutions, data);
        this.bestCurrentSolution = population.getBestSolution();
    }

    public Solution getBestCurrentSolution() {
        return bestCurrentSolution;
    }

    public Population getPopulation() {
        return population;
    }

    /**
     * We make pairs of solution. In each pair, the solution that has the best
     * fitness evaluation is selected with a probability
     * bestSolutionProbability. The selected solutions will then be muted and
     * crossed. The solutions that have the overall best evaluation are always
     * kept in the population (excepted if they are in the same pair).
     *
     * @param bestSolutionProbability
     */
    public void selection(double bestSolutionProbability) {
        ArrayList<Solution> pop = population.getPopulation();
        ArrayList<Solution> newPop = new ArrayList<>();
        shuffle(pop);
        Solution sol1, sol2, selectedSol;
        double randomDouble;
        for (int i = 0; i < pop.size(); i += 2) {
            if (pop.get(i + 1) != null) {
                sol1 = pop.get(i);
                sol2 = pop.get(i + 1);
                if (sol1.getSolutionEvaluation() == bestCurrentSolution.getSolutionEvaluation()) {
                    selectedSol = sol1;
                } else if (sol2.getSolutionEvaluation() == bestCurrentSolution.getSolutionEvaluation()) {
                    selectedSol = sol2;
                } else if (sol1.getSolutionEvaluation() < sol2.getSolutionEvaluation()) {
                    // We check that the probability in input is between 0 and 1
                    if (bestSolutionProbability > 0 && bestSolutionProbability < 1) {
                        // If the random number generated in [0;1[ is less than the
                        // probability in input, we keep the solution that has the 
                        // best evaluation
                        randomDouble = Math.random();
                        if (randomDouble < bestSolutionProbability) {
                            selectedSol = sol1;
                        } else {
                            selectedSol = sol2;
                        }
                    } else {
                        selectedSol = sol1;
                    }
                } else {
                    // We check that the probability in input is between 0 and 1
                    if (bestSolutionProbability > 0 || bestSolutionProbability < 1) {
                        // If the random number generated in [0;1[ is less than the
                        // probability in input, we keep the solution that has the 
                        // best evaluation
                        randomDouble = Math.random();
                        if (randomDouble < bestSolutionProbability) {
                            selectedSol = sol2;
                        } else {
                            selectedSol = sol1;
                        }
                    } else {
                        selectedSol = sol2;
                    }
                }
                newPop.add(selectedSol);
            }
        }
        population.setPopulation(newPop);
        this.bestCurrentSolution = population.getBestSolution();
    }

    // In the mutation operator, we interchange two tasks randomly with a 
    // certain probability and replace each solution in the population with the
    // mutate solution. The best current solution is always kept
    public void mutation(double mutationProbability) {
        ArrayList<Solution> pop = population.getPopulation();
        ArrayList<Solution> newPop = new ArrayList<>();

        double randomDouble;
        Random random = new Random();
        int rand1, rand2;
        WorkingTask task1, task2;
        for (Solution sol : pop) {
            Solution newSol;
            randomDouble = Math.random();
            if (randomDouble < mutationProbability && (!sol.equals(bestCurrentSolution))) {
                // We create a list where we put all the tasks to be able to choose 2
                // tasks randomly
                ArrayList<WorkingTask> allTasks = new ArrayList<>();
                ArrayList<ArrayList<WorkingTask>> solutionToMutate = sol.getSolution();

                for (ArrayList<WorkingTask> resource : solutionToMutate) {
                    for (WorkingTask task : resource) {
                        allTasks.add(task);
                    }
                }

                // We then select two random indexes in allTasks
                rand1 = random.nextInt(allTasks.size());
                rand2 = random.nextInt(allTasks.size());
                while (rand1 == rand2) {
                    rand2 = random.nextInt(allTasks.size());
                }
                // We interchange the two tasks in the solution and rebuild a 
                // solution 
                task1 = allTasks.get(rand1);
                task2 = allTasks.get(rand2);
                int task1Index = -1, task2Index = -1;
                int task1ResourceIndex = -1, task2ResourceIndex = -1;
                for (ArrayList<WorkingTask> resource : solutionToMutate) {

                    if (resource.indexOf(task1) != -1) {
                        task1Index = resource.indexOf(task1);
                        task1ResourceIndex = solutionToMutate.indexOf(resource);
                        task2.setRessource(solutionToMutate.indexOf(resource));
                    }
                    if (resource.indexOf(task2) != -1) {
                        task2Index = resource.indexOf(task2);
                        task2ResourceIndex = solutionToMutate.indexOf(resource);
                        task1.setRessource(solutionToMutate.indexOf(resource));
                    }

                }

                solutionToMutate.get(task1ResourceIndex).set(task1Index, task2);
                solutionToMutate.get(task2ResourceIndex).set(task2Index, task1);

                // build new solution
                Reorganizer reorganizer = new Reorganizer(data.getPolyRessourceNb());
                Solution newSolution = new Solution(data.getPolyRessourceNb());
                newSolution.setSolution(solutionToMutate);

                newSol = reorganizer.computeNewSolution(newSolution);

            } else {
                newSol = sol;
            }
            newPop.add(newSol);
        }
        population.setPopulation(newPop);
        this.bestCurrentSolution = population.getBestSolution();
    }

    public void crossover() {
        ArrayList<Solution> pop = population.getPopulation();
        ArrayList<Solution> newPop = new ArrayList<>();

        // We will cross sol1 and sol2 to obtain newSol1 and newSol2.
        // We keep the four solution in the population
        Solution sol1, sol2;
        Random random = new Random();
        int rand;
        for (int i = 0; i < pop.size(); i += 2) {
            if (pop.get(i + 1) != null) {
                sol1 = pop.get(i);
                sol2 = pop.get(i + 1);
                Solution newSol1 = new Solution(data.getPolyRessourceNb());
                Solution newSol2 = new Solution(data.getPolyRessourceNb());
                ArrayList<ArrayList<WorkingTask>> newSol1Array = new ArrayList<>();
                ArrayList<ArrayList<WorkingTask>> newSol2Array = new ArrayList<>();
                ArrayList<ArrayList<WorkingTask>> nonKeptTasksSol1Array = new ArrayList<>();
                ArrayList<ArrayList<WorkingTask>> nonKeptTasksSol2Array = new ArrayList<>();
                // We select a random time to cut the solution. This time is between 
                // 0 and the evaluation of the solution that has the best evaluation
                if (sol1.getSolutionEvaluation() < sol2.getSolutionEvaluation()) {
                    rand = random.nextInt(sol2.getSolutionEvaluation());
                } else {
                    rand = random.nextInt(sol1.getSolutionEvaluation());
                }
                // We split each list of tasks (for each resource of sol1) into two
                // sublists : list of kept tasks, ie the tasks before cut time
                // and list of non kept tasks that will be interchanged with sol2
                splitAccordingToRandomTime(sol1, newSol1Array, nonKeptTasksSol1Array, rand);
                splitAccordingToRandomTime(sol2, newSol2Array, nonKeptTasksSol2Array, rand);

                // We check if a non kept task of sol2 is already in newSol1. In 
                // this case, it is removed from non kept tasks of sol2.
                removeIdenticalTasks(nonKeptTasksSol2Array, newSol1Array);
                removeIdenticalTasks(nonKeptTasksSol1Array, newSol2Array);

                // If the kept tasks of sol2 are not in the kept tasks of sol1,
                // we add these tasks in the same resource in the non kept tasks
                // of sol2(because they will be interchangent with non kept tasks
                // of sol1 and we have to keep all the tasks to generate a new solution
                addKeptTasksFromOtherSol(newSol2Array, newSol1Array, nonKeptTasksSol2Array);
                addKeptTasksFromOtherSol(newSol1Array, newSol2Array, nonKeptTasksSol1Array);

                // We add the non kept tasks of sol2 to newSol1 and vice versa
                addNonkeptToKept(nonKeptTasksSol2Array, newSol1Array);
                addNonkeptToKept(nonKeptTasksSol1Array, newSol2Array);

                newSol1.setSolution(newSol1Array);
                newSol2.setSolution(newSol2Array);

                // Build new solutions newSol1 and newSol2
                Reorganizer reorganizer1 = new Reorganizer(data.getPolyRessourceNb());
                Reorganizer reorganizer2 = new Reorganizer(data.getPolyRessourceNb());

                Solution newSol1ToAdd = reorganizer1.computeNewSolution(newSol1);
                Solution newSol2ToAdd = reorganizer2.computeNewSolution(newSol2);

                newPop.add(newSol1ToAdd);
                newPop.add(sol1);
                newPop.add(newSol2ToAdd);
                newPop.add(sol2);
            }
        }
        population.setPopulation(newPop);
        this.bestCurrentSolution = population.getBestSolution();
    }

    private void splitAccordingToRandomTime(Solution sol,
            ArrayList<ArrayList<WorkingTask>> keptTasks,
            ArrayList<ArrayList<WorkingTask>> nonKeptTasks,
            int rand) {
        for (ArrayList<WorkingTask> resource : sol.getSolution()) {
            ArrayList<WorkingTask> newResKept = new ArrayList<>();
            ArrayList<WorkingTask> newResNonKept = new ArrayList<>();
            for (WorkingTask task : resource) {
                WorkingTask taskToAdd = new WorkingTask(task.getStartTime(),
                        task.getEndTime(), task.getMachineUsed(),
                        task.getProductUsed(), task.getRessourceUsed(), task.getTask());
                if (task.getStartTime() < rand) {
                    newResKept.add(taskToAdd);
                } else if (task.getStartTime() >= rand) {
                    newResNonKept.add(taskToAdd);
                }
            }
            // We add the two lists of tasks to the corresponding list of resources
            keptTasks.add(newResKept);
            nonKeptTasks.add(newResNonKept);
        }
    }

    private void removeIdenticalTasks(ArrayList<ArrayList<WorkingTask>> nonKeptTasksSol2,
            ArrayList<ArrayList<WorkingTask>> keptTasksSol1) {
        int[] indexes = new int[data.getMachineNb() * data.getProductNb()];
        for (int i = 0; i < data.getMachineNb() * data.getProductNb(); i++) {
            indexes[i] = -1;
        }
        for (ArrayList<WorkingTask> nonKeptResource : nonKeptTasksSol2) {
            int i = 0;
            int currentResource = nonKeptTasksSol2.indexOf(nonKeptResource);
            for (WorkingTask nonKeptTask : nonKeptResource) {
                for (ArrayList<WorkingTask> keptResource : keptTasksSol1) {
                    for (WorkingTask keptTask : keptResource) {
                        if (nonKeptTask.getMachineUsed() == keptTask.getMachineUsed()
                                && nonKeptTask.getProductUsed() == keptTask.getProductUsed()) {
                            indexes[i] = nonKeptResource.indexOf(nonKeptTask);
                            i++;
                        }
                    }
                }
            }
            i--;
            while (i >= 0) {
                nonKeptTasksSol2.get(currentResource).remove(indexes[i]);
                indexes[i] = -1;
                i--;
            }
        }
    }

    private void addKeptTasksFromOtherSol(ArrayList<ArrayList<WorkingTask>> keptTasksSol2,
            ArrayList<ArrayList<WorkingTask>> keptTasksSol1,
            ArrayList<ArrayList<WorkingTask>> nonKeptTasksSol2Array) {
        WorkingTask[] tasksToAdd = new WorkingTask[data.getMachineNb() * data.getProductNb()];
        for (int i = 0; i < data.getMachineNb() * data.getProductNb(); i++) {
            tasksToAdd[i] = null;
        }
        for (ArrayList<WorkingTask> keptResourceSol2 : keptTasksSol2) {
            int i = 0;
            for (WorkingTask keptTaskSol2 : keptResourceSol2) {
                boolean taskFound = false;
                for (ArrayList<WorkingTask> keptResourceSol1 : keptTasksSol1) {
                    for (WorkingTask keptTaskSol1 : keptResourceSol1) {
                        if (keptTaskSol1.getMachineUsed() == keptTaskSol2.getMachineUsed()
                                && keptTaskSol1.getProductUsed() == keptTaskSol2.getProductUsed()) {
                            taskFound = true;
                        }
                    }
                }
                if (!taskFound) {
                    tasksToAdd[i] = keptTaskSol2;
                    i++;
                }
            }
            i--;
            while (i >= 0) {
                int resourceIndex = keptTasksSol2.indexOf(keptResourceSol2);
                nonKeptTasksSol2Array.get(resourceIndex).add(tasksToAdd[i]);
                tasksToAdd[i] = null;
                i--;
            }
        }
    }

    private void addNonkeptToKept(ArrayList<ArrayList<WorkingTask>> nonKeptTasksSol2,
            ArrayList<ArrayList<WorkingTask>> newSol1) {
        for (ArrayList<WorkingTask> resource : newSol1) {
            resource.addAll(nonKeptTasksSol2.get(newSol1.indexOf(resource)));
        }
    }

}
